package com.tianji.promotion.service.impl;

import com.tianji.common.autoconfigure.xxljob.XxlJobProperties;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.domain.dto.CouponDiscountDTO;
import com.tianji.promotion.domain.dto.OrderCouponDTO;
import com.tianji.promotion.domain.dto.OrderCourseDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.IDiscountService;
import com.tianji.promotion.strategy.discount.Discount;
import com.tianji.promotion.strategy.discount.DiscountStrategy;
import com.tianji.promotion.utils.PermuteUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class DiscountServiceImpl implements IDiscountService {

    @Autowired
    private UserCouponMapper userCouponMapper;

    @Autowired
    private ICouponScopeService scopeService;

    @Autowired
    private Executor discountSolutionExecutor;

    private Map<Coupon,List<OrderCourseDTO>> findAvailableCoupon(
            List<Coupon> coupons,List<OrderCourseDTO> courses
    ){
        HashMap<Coupon,List<OrderCourseDTO>> map = new HashMap<>(coupons.size());
        for (Coupon coupon : coupons) {
            //1.找出优惠券的可用教程
            List<OrderCourseDTO> availableCourses=courses;
            if(coupon.getSpecific()){
                //1.1 限定了范围，查询券的可用范围
                List<CouponScope> scopes = scopeService.lambdaQuery().eq(CouponScope::getCouponId,
                        coupon.getId()).list();
                //1.2 获取范围内对应的分类id
                Set<Long> scopeIds = scopes.stream().map(CouponScope::getBizId).collect(Collectors.toSet());
                //1.3 筛选课程
                availableCourses = courses.stream()
                        .filter(c -> scopeIds.contains(c.getCateId())).collect(Collectors.toList());
            }
            if(CollUtils.isEmpty(availableCourses)){
                //没有任何可用课程，抛弃
                continue;
            }
            //2. 计算课程总价
            int totalAmount = availableCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();
            //3. 判断是否可用
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if(discount.canUse(totalAmount,coupon)){
                map.put(coupon,availableCourses);
            }
        }
        return map;
    }


    //查询我的优惠券可用方案
    @Override
    public List<CouponDiscountDTO> findDiscountSolution(List<OrderCourseDTO> orderCourses) {
        //1. 查询我的可用优惠券信息
        List<Coupon> coupons = userCouponMapper.queryMyCoupons(UserContext.getUser());
        if(CollUtils.isEmpty(coupons)){
            return CollUtils.emptyList();
        }
        //2.初步筛选
        int totalAmount = orderCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();
        List<Coupon> availableCoupons = coupons.stream()
                .filter(c -> DiscountStrategy.getDiscount(c.getDiscountType()).canUse(totalAmount, c))
                .collect(Collectors.toList());
        if(CollUtils.isEmpty(availableCoupons)){
            return CollUtils.emptyList();
        }
        //3. 数据细筛
        Map<Coupon, List<OrderCourseDTO>> relatedMap = findAvailableCoupon(coupons, orderCourses);
        if(CollUtils.isEmpty(relatedMap)){
            return CollUtils.emptyList();
        }
        //4. 对可用优惠券排列组合
        availableCoupons = new ArrayList<>(relatedMap.keySet());
        List<List<Coupon>> solutions = PermuteUtil.permute(availableCoupons);
        //添加单券的方案()
        for (Coupon c : availableCoupons) {
            solutions.add(List.of(c));
        }

        //5.并发计算(计算方案的优惠明细)
        List<CouponDiscountDTO> list = Collections.synchronizedList(new ArrayList<>(solutions.size()));
        // 5.1 定义锁
        CountDownLatch latch = new CountDownLatch(solutions.size());
        for (List<Coupon> solution : solutions) {
            //5.2 异步计算
            CompletableFuture.supplyAsync(
                    ()->calculateSolutionDiscount(relatedMap,orderCourses,solution),
                    discountSolutionExecutor
            ).thenAccept(dto->{
                //5.3 提交任务结果
                list.add(dto);
                latch.countDown();
            });
        }
        //5.4 等待运算结束
        try {
            latch.await(1, TimeUnit.SECONDS);
        }catch (InterruptedException e){
            log.error("优惠方案计算异常, {}",e.getMessage());
        }



        //6.筛选最优解
        return findBestSolution(list);
    }

    //根据券方案计算订单优惠明细
    @Override
    public CouponDiscountDTO queryDiscountDetailByOrder(OrderCouponDTO orderCouponDTO) {
        //1. 查询用户优惠券
        List<Long> userCouponIds = orderCouponDTO.getUserCouponIds();
        List<Coupon> coupons= userCouponMapper.queryCouponByUserCouponIds(userCouponIds, UserCouponStatus.UNUSED);
        if(CollUtils.isEmpty(coupons)){
            return null;
        }
        //2. 查询优惠券对应课程
        Map<Coupon, List<OrderCourseDTO>> availableCouponMap = findAvailableCoupon(coupons, orderCouponDTO.getCourseList());
        if(CollUtils.isEmpty(availableCouponMap)){
            return null;
        }
        //3. 查询优惠券规则
        return calculateSolutionDiscount(availableCouponMap,orderCouponDTO.getCourseList(),coupons);

    }

    private List<CouponDiscountDTO> findBestSolution(List<CouponDiscountDTO> list) {
        //1. 准备Map记录最优解
        HashMap<String, CouponDiscountDTO> moreDiscountMap = new HashMap<>();
        HashMap<Integer, CouponDiscountDTO> lessCounponMap = new HashMap<>();
        //2. 遍历，筛选最优解
        for (CouponDiscountDTO solution : list) {
            //2.1 计算当前方案的id组合(升序排序，1-2-3/3-2-1最后得到key都素hi1-2-3，从而德方案一最优)
            String ids = solution.getIds().stream()
                    .sorted(Long::compare).map(String::valueOf).collect(Collectors.joining(","));
            // 2.2比较用券相同时，优惠金额是否最大
            CouponDiscountDTO best = moreDiscountMap.get(ids);
            if(best != null && best.getDiscountAmount() >= solution.getDiscountAmount()){
                // 当前方案优惠金额少，跳过
                continue;
            }
            //2.3 比较金额相同时，用券数量是否最少
            best = lessCounponMap.get(solution.getDiscountAmount());
            int size=solution.getIds().size();
            if(size > 1 && best != null && best.getIds().size() <= size){
                //当前方案用券更多，放弃
                continue;
            }
            //2.4 更新最优解
            moreDiscountMap.put(ids,solution);
            lessCounponMap.put(solution.getDiscountAmount(),solution);
        }
        //3.求交集
        Collection<CouponDiscountDTO> beseSolutions = CollUtils
                .intersection(moreDiscountMap.values(), lessCounponMap.values());
        //4. 排序，按优惠金额排序
        return beseSolutions.stream()
                .sorted(Comparator.comparingInt(CouponDiscountDTO::getDiscountAmount).reversed())
                .collect(Collectors.toList());

    }

    // 计算方案的优惠明细
    private CouponDiscountDTO calculateSolutionDiscount
    (Map<Coupon, List<OrderCourseDTO>> couponMap, List<OrderCourseDTO> courses, List<Coupon> solution) {
        // 1. 初始化DTO
        CouponDiscountDTO dto = new CouponDiscountDTO();
        //2. 初始化折扣明细的映射
        Map<Long, Integer> detailMap = courses.stream().collect(Collectors.toMap(OrderCourseDTO::getId, oc -> 0));
       dto.setDiscountDetail(detailMap);
        //3. 计算折扣
        for (Coupon coupon : solution) {
            //3.1 获取优惠券限定范围对应的课程
            List<OrderCourseDTO> availableCourses = couponMap.get(coupon);
            //3.2 计算课程总价(课程原价-折扣明细)
            int totalAmount = availableCourses.stream()
                    .mapToInt(oc -> oc.getPrice() - detailMap.get(oc.getId())).sum();
            //3.3 判断是否可用
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if(!discount.canUse(totalAmount,coupon)){
                //券不可用，跳过
                continue;
            }
            //3.4 计算优惠金额
            int discountAmount = discount.calculateDiscount(totalAmount, coupon);
            //3.5 计算优惠明细
            calculateDiscountDetails(detailMap,availableCourses,totalAmount,discountAmount);
            //3.6 更新DTO数据
            dto.getIds().add(coupon.getId());
            dto.getRules().add(discount.getRule(coupon));
            dto.setDiscountAmount(discountAmount+dto.getDiscountAmount());
        }
        return dto;


    }

    private void calculateDiscountDetails(Map<Long, Integer> detailMap, List<OrderCourseDTO> courses,
                                          int totalAmount, int discountAmount) {
        int times=0;
        int remainDiscount=discountAmount;
        for (OrderCourseDTO course : courses) {
            // 更新课程已计算数量
            times++;
            int discount=0;
            //判断是否是最后一个课程
            if(times==courses.size()){
                //市最后一个课程，总折扣金额-之前所有商品的折扣金额之和
                discount=remainDiscount;
            }else {
                //计算四年折扣明细(课程价格在总价中站的比例，乘以总的折扣)
                discount=discountAmount*course.getPrice()/totalAmount;
                remainDiscount -= discount;
            }
            //更新折扣明细
            detailMap.put(course.getId(),discount+detailMap.get(course.getId()));
        }
    }
}
